// // Copyright (c) 2009 All Right Reserved // // vl // // 2009-01-01 // Contains ... using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Text; using System.Xml.Linq; using JetBrains.Annotations; namespace LargoCommon.Music { /// /// Musical Block Model. /// [Serializable] public sealed class RhythmicModel : AbstractModel, ICloneable { #region Fields /// /// Rhythmic motives. /// [NonSerialized] private IEnumerable rhythmicMotives; /// Used Rhythmic Motives. private Dictionary usedRhythmicMotives; #endregion #region Constructors /// /// Initializes a new instance of the class. /// public RhythmicModel() { this.RhythmicMotives = new List(); } /// /// Initializes a new instance of the class. /// /// The mark block model. public RhythmicModel(XElement markBlockModel) { this.RhythmicMotives = new List(); } #endregion #region Properties - Xml /// Gets Xml representation. /// Property description. public new XElement GetXElement { get { var xe = new XElement( "Rhythmic", new XAttribute("Name", this.Name), new XAttribute("Order", this.RhythmicOrder)); var xmotives = new XElement("Motives"); foreach (var rstruct in this.RhythmicMotives) { var xmotive = rstruct.GetXElement; xmotives.Add(xmotive); } xe.Add(xmotives); return xe; } } #endregion #region Properties /// /// Gets or sets the rhythmic order. /// /// /// The rhythmic order. /// public byte RhythmicOrder { get; set; } /// /// Gets or sets the rhythmic motives. /// /// /// The rhythmic motives. /// public IEnumerable RhythmicMotives { get { Contract.Ensures(Contract.Result>() != null); if (this.rhythmicMotives == null) { throw new InvalidOperationException("Rhythmic motives are null."); } return this.rhythmicMotives; } set => this.rhythmicMotives = value ?? throw new ArgumentException("Argument cannot be empty.", nameof(value)); } /// /// Gets the rhythmic structures by variance. /// /// Property description. public IList RhythmicStructuresOfMotives { get { Contract.Ensures(Contract.Result>() != null); var rs = new List(); foreach (var m in this.RhythmicMotives) { rs.AddRange(m.RhythmicStructures); } var structs = (from r in rs select r).Distinct().ToList(); //// var sortedStructs = from rx in structs orderby rx.Level, rx.Variance select rx; //// rx.ToneLevel rx.ElementSchema //// OrderBy(x => x.Level.ToString().PadLeft(3) + x.ElementSchema).ToList(); ////. x.ElementSchema); return structs; } } #endregion #region Other public properties /// /// Gets the first melodic bar. /// /// Property description. [UsedImplicitly] public int FirstMelodicBar { get { var barNumber = (from c in this.BlockChanges.Changes where c.IsMelodicalNature orderby c.BarNumber select c.BarNumber).FirstOrDefault(); return barNumber; } } #endregion #region Static factory methods /// /// Extracts the musical block model. /// /// The musical block. /// /// Returns value. /// [UsedImplicitly] public static RhythmicModel GetNewModel(MusicalBlock musicalBlock) { musicalBlock.Header.NumberOfLines = (byte)musicalBlock.Strip.Lines.Count; var model = GetNewModel(musicalBlock.Header.Name, musicalBlock); model.Number = musicalBlock.Header.Number; model.SourceMusicalBlock = musicalBlock; model.Header = musicalBlock.Header; return model; } /// /// Gets the new musical model. /// /// Name of the model. /// The musical block. /// /// Returns value. /// /// Null Exception. public static RhythmicModel GetNewModel(string modelName, MusicalBlock musicalBlock) { Contract.Requires(musicalBlock != null); var model = new RhythmicModel { Name = modelName, IsSelected = false }; if (model == null) { throw new ArgumentNullException(nameof(modelName)); } model.Header = musicalBlock.Header; model.SourceMusicalBlock = musicalBlock; model.RhythmicOrder = model.Header.System.RhythmicOrder; return model; } #endregion #region Public methods /* 2018/10 /// /// Gets the rhythmic structures. /// /// The number of top rhythmic structures to select. /// The regularity. /// From level. /// To level. /// /// Returns value. /// [UsedImplicitly] public IList GetRhythmicStructures(int numberOfTopRhythmicStructuresToSelect, RegularityFactor regularity, byte fromLevel, byte toLevel) { //// Warning 9 CodeContracts: Member 'LargoObjectMusic.Core.RhythmicCore.rhythmicMotives' has less visibility than the enclosing method //// 'LargoObjectMusic.Core.RhythmicCore.GetRhythmicStructures(System.Int32,System.Boolean,System.Byte,System.Byte)' Contract.Requires(this.rhythmicMotives != null); List list; if (regularity == RegularityFactor.Regular) { list = this.RegularRhythmicStructures(2).ToList(); } else { list = (from rs in this.RhythmicStructuresOfMotives where rs.FormalBehavior.Variance > 1 && rs.FormalBehavior.Variance < DefaultValue.NinetyNine && (rs.ZeroStart || rs.ToneLevel == 0) select rs).ToList(); } var resultList = (from rs in list where rs.ToneLevel >= fromLevel && rs.ToneLevel <= toLevel orderby rs.ToneLevel descending, rs.RhythmicBehavior.Filling descending, rs.FormalBehavior.Variance descending select rs).Take(numberOfTopRhythmicStructuresToSelect).ToList(); return resultList; } */ /// /// Converts to order. /// /// The given system. [UsedImplicitly] public void ConvertToSystem(RhythmicSystem givenSystem) { Contract.Requires(givenSystem != null); this.RhythmicOrder = givenSystem.Order; var cnt = this.RhythmicMotives.Count(); if (cnt == 0) { return; } foreach (var m in this.RhythmicMotives) { m.ConvertToSystem(givenSystem); } } /// /// Gets the rhythmic motive. /// /// The number. /// Returns value. [UsedImplicitly] public RhythmicMotive GetRhythmicMotive(int number) { var cnt = this.RhythmicMotives.Count(); if (cnt == 0) { return null; } var localNumber = number; if (localNumber > cnt) { checked { localNumber = ((number - 1) % cnt) + 1; } } //// RhythmicMotive motive = this.RhythmicMotives.ElementAt(localNumber); var motive = (from m in this.RhythmicMotives where m.Number == localNumber select m).FirstOrDefault(); return motive; } /// /// Adds the motive. /// /// The motive. public void AddMotive(RhythmicMotive motive) { Contract.Requires(motive != null); //// motive.Core = this; ((List)this.RhythmicMotives).Add(motive); } #endregion /// /// Appends the rhythmic motives. /// Main algorithm to determine motivic classes and their instances /// /// The item groups. public void AppendRhythmicMotives(List itemGroups) { var lastRhythmicIdentifier = string.Empty; //// All motivic items ordered by length (descending) and identifier var rhythmicItemGroups = (from ig in itemGroups orderby ig.Length descending, ig.RhythmicIdentifier select ig).ToList(); RhythmicMotive rhythmicMotive = null; //// var numberWithinLength = 0; var lastLength = 0; foreach (var itemGroup in rhythmicItemGroups) { //// if (itemGroup.Length != lastLength) { numberWithinLength = 0; lastLength = itemGroup.Length; } var ident = itemGroup.RhythmicIdentifier; if (ident != lastRhythmicIdentifier) { //// Step to next new motive rhythmicMotive = itemGroup.RhythmicMotive(0, string.Empty); if (rhythmicMotive == null) { return; } this.AddMotive(rhythmicMotive); lastRhythmicIdentifier = ident; } if (rhythmicMotive != null) { rhythmicMotive.Occurrence++; var area = itemGroup.GetArea(); this.SourceMusicalBlock.Body.MarkRhythmicMotive(rhythmicMotive, area); } } this.CompleteMotives(); } /// /// Completes the motives. /// public void CompleteMotives() { var rhythmicMotiveNumber = 0; var orderedMotives = (from m in this.RhythmicMotives orderby m.Occurrence descending, m.Length descending select m).ToList(); foreach (var motive in orderedMotives) { rhythmicMotiveNumber++; //// string motiveName = string.Format(CultureInfo.InvariantCulture,"T{0}/R{1}", ("0" + musicalTrack.LineIndex.ToString(CultureInfo.CurrentCulture)).Right(2), ("0000" + this.MelodicAnalyzer.RhythmicMotiveNumber.ToString(CultureInfo.CurrentCulture)).Right(4)); //// var motiveName = string.Format(CultureInfo.InvariantCulture, "R{0}", ("0000" + rhythmicMotiveNumber.ToString(CultureInfo.CurrentCulture)).Right(4)); var motiveName = MusicalProperties.GetMotiveName(string.Empty, rhythmicMotiveNumber, motive.Length); //// rhythmicMotiveNumber motive.Number = rhythmicMotiveNumber; motive.Name = motiveName; } } #region Unique motives /// /// Get UniqueTRhythmicMotive. /// /// Unique Identifier. /// Returns value. [UsedImplicitly] public RhythmicMotive GetUniqueRhythmicMotive(string uniqueIdentifier) { Contract.Requires(uniqueIdentifier != null); if (this.usedRhythmicMotives == null) { this.usedRhythmicMotives = new Dictionary(); } var trm = this.usedRhythmicMotives.ContainsKey(uniqueIdentifier) ? this.usedRhythmicMotives[uniqueIdentifier] : null; if (trm != null) { return trm; } //// Lock needed here var rhythmicMotiveList = this.RhythmicMotives; foreach (var rhythmicMotive in rhythmicMotiveList.Where(rhythmicMotive => rhythmicMotive != null && string.CompareOrdinal(rhythmicMotive.UniqueIdentifier, uniqueIdentifier) == 0)) { this.usedRhythmicMotives[uniqueIdentifier] = rhythmicMotive; return rhythmicMotive; //// Avoid multiple or conditional return statements. } return null; } #endregion #region String representation /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// [UsedImplicitly] public override string ToString() { return this.RhythmicOrder.ToString(CultureInfo.InvariantCulture); } #endregion /// Makes a deep copy of the BlockModel object. /// Returns object. public object Clone() { var model = new RhythmicModel { Name = this.Name, Number = this.Number, //// Core = this.Core, Header = (MusicalHeader)this.Header.Clone(), SourceMusicalBlock = this.SourceMusicalBlock }; return model; } #region Material Extractor /// /// Extract rhythmic material. /// /// /// Returns value. /// public RhythmicMaterial ExtractRhythmicMaterial() { //// var dcm = DataBridgeMaterial.GetMaterialContext; var material = new RhythmicMaterial { Name = this.Name, RhythmicOrder = this.RhythmicOrder }; //// , CoreId = rhythmicCore.TRhythmicCore.Id //// dcm.AddToTRhythmicMaterial(trm); var list = new Collection(); // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (var motive in this.RhythmicMotives) { // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (var structure in motive.RhythmicStructures) { var sc = structure.GetStructuralCode; if (string.IsNullOrEmpty(sc)) { continue; } list.Add(structure); } } //// Grouping var groupList = (from ms in list group ms by ms.GetStructuralCode into g select g).ToList(); foreach (var g in groupList) { var s = g.FirstOrDefault(); if (s == null) { continue; } var ms = s; //// Clone? //// new RhythmicStructure(s.RhythmicSystem, s.GetStructuralCode()) ms.Occurrence = g.Count(); ms.DetermineLevel(); //// 2019/01 ms.DetermineBehavior(); //// 2019/01 material.Structures.Add(ms); } //// dcm.SaveChanges(); return material; } #endregion #region Private methods /// /// Regulars the rhythmic structures. /// /// The metric base. /// /// Returns value. /// [UsedImplicitly] private IEnumerable RegularRhythmicStructures(byte metricBase) { Contract.Ensures(Contract.Result>() != null); var rsystem = RhythmicSystem.GetRhythmicSystem(RhythmicDegree.Structure, this.RhythmicOrder); var structs = new List(); var s1 = string.Format(CultureInfo.CurrentCulture, "1,{0}*0,", this.RhythmicOrder - 1); var r1 = new RhythmicStructure(rsystem, s1); r1.DetermineBehavior(); structs.Add(r1); var s1P = string.Format(CultureInfo.CurrentCulture, "2,{0}*0,", this.RhythmicOrder - 1); var r1P = new RhythmicStructure(rsystem, s1P); r1P.DetermineBehavior(); structs.Add(r1P); for (var d = 1; d < this.RhythmicOrder - 1; d++) { if (d % metricBase != 0) { continue; } if (this.RhythmicOrder % d != 0) { continue; } var sb = new StringBuilder(); var length = this.RhythmicOrder / d; var s = string.Format(CultureInfo.CurrentCulture, "1,{0}*0,", length - 1); for (var i = 0; i < d; i++) { sb.Append(s); } sb.Remove(sb.Length - 1, 1); var rs = new RhythmicStructure(rsystem, sb.ToString()); rs.DetermineBehavior(); structs.Add(rs); } return structs; } #endregion } }